//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
// (c) trueblacker@mail.ru, tiled export code contribution, 2006
//
// OziExplorer MAP (raster maps) export.
//

# include "StdAfx.h"
# include "Save.h"
# include "Map.h"
# include "OziMapWizard.h"
# include "Options.h"
# include "Position.h"
# include "Scale.h"
# include "Status.h"
# include "MapView.h"
# include "EditRect.h"
# include "DDBitmap.h"
# include "FileWriter.h"
# include "Wrappers.h"
# include "ParseHelpers.h"
# include "fast_floor.h"
# include "fast_log2.h"
# include "Globals.h"

class COziMapExport : public CMapSaver {
  public:
	virtual bool CanSave (const map_t & _map) {return true;}
	virtual bool Save (HANDLE _hFile, const char * _strFileName, const map_t & _map);

  private:
	static bool ExportMap (
		const map_t &  _map,
		const rect_t & _rect,
		size_t         _cXAmount,
		size_t         _cYAmount,
		const char *   _strBaseFileName
	);

	static bool ExportTile (
		const map_t &  _map,
		const rect_t & _rect,
		float          _fProgress0,
		float          _fProgressStep,
		const char *   _strBmpFileName,
		const char *   _strMapFileName
	);

	static void MakeFilePath (
		const char * _strBaseFileName,
		size_t       _cX,
		size_t       _cY,
		size_t       _cNumWidth,
		const char * _strExt,
		char         _strFilePath [MAX_PATH]
	);
};

DECLARE_MAP_EXPORT (COziMapExport, "ozi-map", "map", "OziExplorer map");

void COziMapExport::MakeFilePath (
	const char * _strBaseFileName,
	size_t       _cX,
	size_t       _cY,
	size_t       _cNumWidth,
	const char * _strExt,
	char         _strFilePath [MAX_PATH]
) {
	const size_t cTotalSize = ::strlen (_strBaseFileName) 
		+ 2 // 2 "_" characters
		+ 1 // dot for extension
		+ _cNumWidth*2
		+ ::strlen (_strExt);
	if (cTotalSize >= MAX_PATH) {
		_strFilePath [0] = '\0';
		return;
	}

	char strWidth [10];
	::itoa (_cNumWidth, strWidth, 10);

	char strMask [32] = "%s_%0";
	::strcat (strMask, strWidth);
	::strcat (strMask, "d_%0");
	::strcat (strMask, strWidth);
	::strcat (strMask, "d.%s");

	::sprintf (_strFilePath, strMask, _strBaseFileName, _cX, _cY, _strExt);
}

bool COziMapExport::ExportMap (
	const map_t &  _map,
	const rect_t & _rect,
	size_t         _cXAmount,
	size_t         _cYAmount,
	const char *   _strBaseFileName
) {
	const float fXStep = _rect.Width  ()/_cXAmount;
	const float fYStep = _rect.Height ()/_cYAmount;

	char strTmp [20];
	const size_t cNumXWidth = ::strlen (::itoa (_cXAmount - 1, strTmp, 10));
	const size_t cNumYWidth = ::strlen (::itoa (_cYAmount - 1, strTmp, 10));
	const size_t cNumWidth = __max (cNumXWidth, cNumYWidth);

	rect_t rectCur;
	rectCur.y0 = _rect.y1 - fYStep;
	rectCur.y1 = _rect.y1;

	float fProgress = 0;
	float fProgressStep = 100.f/(_cYAmount*_cXAmount);

	for (size_t i = 0; i < _cYAmount; ++ i) {
		rectCur.x0 = _rect.x0;
		rectCur.x1 = rectCur.x0 + fXStep;
		for (size_t j = 0; j < _cXAmount; ++ j) {
			// Prepare file names for the tile.
			char strBmpFilePath [MAX_PATH];
			MakeFilePath (_strBaseFileName, j, i, cNumWidth, "bmp", strBmpFilePath);
			char strMapFilePath [MAX_PATH];
			MakeFilePath (_strBaseFileName, j, i, cNumWidth, "map", strMapFilePath);

			if (! ExportTile (_map, rectCur, fProgress, fProgressStep, strBmpFilePath, strMapFilePath))
				return false;

			fProgress += fProgressStep;
			SetProgressStatus (fast_floor (fProgress + .5f));

			rectCur.Shift (fXStep, 0);
		}
		rectCur.Shift (0, -fYStep);
	}
	return true;
}

bool COziMapExport::ExportTile (
	const map_t &  _map,
	const rect_t & _rect,
	float          _fProgress0,
	float          _fProgressStep,
	const char *   _strBmpFileName,
	const char *   _strMapFileName
) {
	//
	// Get rectangle.
	//
	rect_t rect;
	int X0, X1, Y0, Y1;
	{
		CCritSec::CLock lock (g_cs);

		rect_t::Intersection (rect, _map.rectBound, _rect);

		X0 = XToScreen (rect.x0);
		X1 = XToScreen (rect.x1);
		Y0 = YToScreen (rect.y0);
		Y1 = YToScreen (rect.y1);
	}
	const size_t cWidth  = X1 - X0 + 1;
	const size_t cHeight = Y0 - Y1 + 1;

	const size_t cScanWidth = Align4 (3*cWidth);
	const ULONGLONG ullDataSize = ULONGLONG (cScanWidth)*cHeight;
	if (ullDataSize > 0xFFFFFFFF) {
		if (
			YesNoMsgBox (
				"The BMP files over 4 GB may be not readable in most of software.\n"
				"Do you want to continue?"
			) == false
		)
			return false;
	}

	//	
	// Create .bmp file.
	//
	const CFileForWrite hFileBmp = _strBmpFileName;
	if (! hFileBmp.IsValid ()) {
		ErrorMsgBox ("Can't create bitmap file!");
		return false;
	}

	CFileWriter fwBmp (hFileBmp);

	// Save bitmap header.
	BITMAPFILEHEADER fhdr = {'MB', sizeof (BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER) + ullDataSize, 0, 0, sizeof (BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER)};
	fwBmp.WriteBin (& fhdr, sizeof (fhdr));

	BITMAPINFOHEADER ihdr = {sizeof (BITMAPINFOHEADER), cWidth, cHeight, 1, 24, BI_RGB, 0, c_pixels_per_cm*100, c_pixels_per_cm*100, 0, 0};
	fwBmp.WriteBin (& ihdr, sizeof (ihdr));

	//
	// Render bitmap by parts.
	// NOTE: there is limitation in bitmap size < 16M in some OS/videodrivers.
	//

	// Bits per pixel for DDB.
	const size_t cBPP = ::GetDeviceCaps (CClientDC (NULL), BITSPIXEL);
	// Size of full DDB.
	const ULONGLONG ullSize = ULONGLONG (cWidth)*cHeight*cBPP/8;

	const size_t cParts = ullSize/16000000 + 1;
	const size_t cPartHeight0 = cHeight/cParts;
	if (cPartHeight0 == 0) {
		ErrorMsgBox ("The export area width (in pixels) is too huge.");
		return false;
	}

	// Prepare the buffer to get DIB bits.
	const size_t c_cScanLinesInBuffer = 128;
	BYTE * const pBits = new BYTE [cScanWidth*c_cScanLinesInBuffer];
	CByteArrayHolder pBitsHolder (pBits);

	// Prepare DDB bitmap.
	ihdr.biHeight = cPartHeight0;
	ihdr.biSizeImage = cScanWidth*cPartHeight0;
	CDDBitmap bitmap (cWidth, cPartHeight0);
	if (bitmap.GetBitmap () == NULL) {
		ErrorMsgBox ("Not enough free video memory!");
		return false;
	}

	const HBRUSH hbrBG = ::CreateSolidBrush (GetBackgroundColor ());
	CGdiObjectHolder hbrBG_Holder (hbrBG);

	for (size_t cScan = 0; cScan < cHeight; cScan += cPartHeight0) {
		const size_t cPartHeight = cScan + cPartHeight0 <= cHeight ? cPartHeight0 : cHeight - cScan;
		assert (cPartHeight <= cPartHeight0);
		size_t cScan0 = cHeight - cScan - cPartHeight0;

		const HDC hdcMem = bitmap.GetDC ();

		// Erase background.
		::SetViewportOrgEx (hdcMem, 0, 0, NULL);
		RECT rct = {0, 0, cWidth, cPartHeight0};
		::FillRect (hdcMem, & rct, hbrBG);

		//
		::SetViewportOrgEx (hdcMem, -X0, -Y1 - int (cScan0), NULL);

		rect_t rectPart;
		rectPart.x0 = rect.x0;
		rectPart.x1 = rect.x1;
		rectPart.y1 = rect.y1 - int (cScan0)*rect.Height ()/cHeight;
		rectPart.y0 = rectPart.y1 - cPartHeight*rect.Height ()/cHeight;

		// Render the part.
		{
			CCritSec::CLock lock (g_cs);

			_map.Draw (
				hdcMem, rectPart,
				fast_floor (_fProgress0 + .5f + _fProgressStep*cScan                  /cHeight),
				fast_floor (_fProgress0 + .5f + _fProgressStep*(cScan + cPartHeight/2)/cHeight),
				g_bShowAttachments
			);
		}

		// Get bitmap bits.
		for (size_t cScanLine = 0; cScanLine < cPartHeight; cScanLine += c_cScanLinesInBuffer) {
			const size_t cScanLines = cPartHeight - cScanLine > c_cScanLinesInBuffer ? c_cScanLinesInBuffer : cPartHeight - cScanLine;
			assert (cScanLines <= c_cScanLinesInBuffer);
			::GetDIBits (
				hdcMem, bitmap.GetBitmap (), 
				cScanLine, cScanLines,
				pBits, reinterpret_cast<BITMAPINFO *> (& ihdr), DIB_RGB_COLORS
			);

			fwBmp.WriteBin (pBits, cScanWidth*cScanLines);

			SetProgressStatus (fast_floor (_fProgress0 + _fProgressStep*(cScan + cPartHeight/2 + cScanLine/2)/cHeight + .5f));
		}
	}

	fwBmp.FlushBuffer ();

	if (fwBmp.GetOverflow ())
		ErrorMsgBox ("File has been written partialy.\nThis may be caused by disk overflow.");

	//
	// Create OziExplorer .map file
	//
	const CFileForWrite hFileMap (_strMapFileName);
	if (! hFileMap.IsValid ()) {
		ErrorMsgBox ("Can't create .map file!");
		return false;
	}

	CFileWriter fwMap (hFileMap);
	fwMap.WriteS ("OziExplorer Map Data File Version 2.1\r\n");
	fwMap.WriteS (_map.Header.strName);
	fwMap.WriteS ("\r\n");
	fwMap.WriteS (_strBmpFileName);
	fwMap.WriteS ("\r\n");
	fwMap.WriteS (
		"1   TIFF scale factor\r\n"
		"WGS 84,,   0.0000,   0.0000,WGS 84\r\n"
		"Reserved 1\r\n"
		"Reserved 2\r\n"
		"Magnetic Variation,,,W\r\n"
		"Map Projection,Latitude/Longitude,PolyCal,No,AutoCalOnly,No,BSBUseWPX,Yes\r\n"
	);

	fwMap.WriteFmt (
		"Point01,xy,    0,    0,in, deg,  %d,  %0.3f,%c,  %d,  %0.3f,%c, grid,   ,           ,           ,N\r\n",
		fast_floor (::fabs (rect.y1)), ::fmod (::fabs (rect.y1), 1)*60, rect.y1 > 0 ? 'N' : 'S',
		fast_floor (::fabs (rect.x0)), ::fmod (::fabs (rect.x0), 1)*60, rect.x0 > 0 ? 'E' : 'W'
	);
	fwMap.WriteFmt (
		"Point02,xy, %d,  %d,in, deg,  %d,  %0.3f,%c,  %d,  %0.3f,%c, grid,   ,           ,           ,N\r\n",
		cWidth - 1, cHeight - 1,
		fast_floor (::fabs (rect.y0)), ::fmod (::fabs (rect.y0), 1)*60, rect.y0 > 0 ? 'N' : 'S',
		fast_floor (::fabs (rect.x1)), ::fmod (::fabs (rect.x1), 1)*60, rect.x1 > 0 ? 'E' : 'W'
	);
	for (size_t c = 3; c <= 30; ++ c) {
		fwMap.WriteS ("Point");
		fwMap.WriteC ('0' + c / 10);
		fwMap.WriteC ('0' + c % 10);
		fwMap.WriteS (",xy,     ,     ,in, deg,    ,        ,N,    ,        ,W, grid,   ,           ,           ,N\r\n");
	}
	fwMap.WriteS (
		"Projection Setup,,,,,,,\r\n"
		"Map Feature = MF ; Map Comment = MC     These follow if they exist\r\n"
		"Track File = TF      These follow if they exist\r\n"
		"Moving Map Stuff = MM?    These follow if they exist\r\n"
		"MM0,Yes\r\n"
	);

	// TODO: Correct rect in 1 pixel ?
	fwMap.WriteS (
		"MMPNUM,4\r\n"
		"MMPXY,1,0,0\r\n"
	);
	fwMap.WriteFmt ("MMPXY,2,%d,0\r\n", cWidth - 1);
	fwMap.WriteFmt ("MMPXY,3,%d,%d\r\n", cWidth - 1, cHeight - 1);
	fwMap.WriteFmt ("MMPXY,4,0,%d\r\n", cHeight - 1);
	fwMap.WriteFmt ("MMPLL,1, %0.6f, %0.6f\r\n", rect.x0, rect.y1);
	fwMap.WriteFmt ("MMPLL,2, %0.6f, %0.6f\r\n", rect.x1, rect.y1);
	fwMap.WriteFmt ("MMPLL,3, %0.6f, %0.6f\r\n", rect.x1, rect.y0);
	fwMap.WriteFmt ("MMPLL,4, %0.6f, %0.6f\r\n", rect.x0, rect.y0);

	fwMap.WriteS   ("MOP,Map Open Position,0,0\r\n");
	fwMap.WriteFmt ("IWH,Map Image Width/Height,%d,%d\r\n", cWidth, cHeight);

	fwMap.FlushBuffer ();

    return true;
}

bool COziMapExport::Save (HANDLE _hFile, const char * _strFileName, const map_t & _map) {
	COziMapWizard wizard;
	if (wizard.Show (_strFileName) != lsOK)
		return false;

	SetProgressStatus (0);

	if (wizard.IsTiled ()) {
		// Trim file name extension.
		std::string strName = wizard.GetBmpFileName().c_str ();
		const size_t iDot = strName.rfind ('.');
		if (iDot != std::string::npos)
			strName.assign (strName.c_str (), iDot);

		ExportMap (_map, wizard.GetRect (), wizard.GetCols (), wizard.GetRows (), strName.c_str ());
    } else {
		// Export map as single tile.
		ExportTile (_map, wizard.GetRect (), 0, 100, wizard.GetBmpFileName ().c_str (), wizard.GetMapFileName ().c_str ());
    }

	SetProgressStatus (100);

	Refresh ();

	return true;
}
